using Microsoft.Extensions.Options;
using RAG.Application.Dtos.Knowledge;
using RAG.Application.Services.Interfaces;
using RAG.Domain.Configuration;
using RAG.Domain.Entities.App;
using RAG.Domain.Repositories;
using RAG.Domain.Services;
using System.Text;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using NPOI.SS.UserModel; // workbook interfaces
using NPOI.HSSF.UserModel; // xls
using NPOI.XSSF.UserModel; // xlsx

namespace RAG.Application.Services.Implementations;

public class KnowledgeBaseService : IKnowledgeBaseService
{
    private readonly IDocumentRepository _documentRepository;
    private readonly IDocumentChunkRepository _chunkRepository;
    private readonly IKnowledgeBaseRepository _knowledgeBaseRepository;
    private readonly IEmbeddingService _embeddingService;
    private readonly ITextChunkingService _chunkingService;
    private readonly RAGSettings _ragSettings;
    private readonly IKnowledgeBaseRepository _kbRepository;

    public KnowledgeBaseService(
        IDocumentRepository documentRepository,
        IDocumentChunkRepository chunkRepository,
        IKnowledgeBaseRepository knowledgeBaseRepository,
        IEmbeddingService embeddingService,
        ITextChunkingService chunkingService,
    IOptions<RAGSettings> ragSettings)
    {
        _documentRepository = documentRepository;
        _chunkRepository = chunkRepository;
        _knowledgeBaseRepository = knowledgeBaseRepository;
        _kbRepository = knowledgeBaseRepository;
        _embeddingService = embeddingService;
        _chunkingService = chunkingService;
        _ragSettings = ragSettings.Value;
    }

    public async Task<DocumentResponseDto> UploadDocumentAsync(KnowledgeUploadDto uploadDto, Guid userId)
    {
        if (uploadDto.File == null) throw new ArgumentException("文件不能为空");
        var originalExt = Path.GetExtension(uploadDto.File.FileName).ToLowerInvariant();
        var allowed = new[] { ".txt", ".doc", ".docx", ".pdf", ".md", ".xls", ".xlsx", ".ppt", ".pptx" }; // 可扩展
        if (!allowed.Contains(originalExt))
        {
            throw new NotSupportedException($"不支持的文件类型: {originalExt}");
        }

        // 生成安全文件名（避免原始名包含特殊字符）
        var fileSafeName = Guid.NewGuid().ToString("N") + originalExt;
        var uploadRoot = Directory.Exists("uploads") ? "uploads" : (Directory.CreateDirectory("uploads").FullName);
        var filePath = Path.Combine(uploadRoot, fileSafeName);

        try
        {
            await using (var stream = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
            {
                await uploadDto.File.CopyToAsync(stream);
            }
        }
        catch (Exception ex)
        {
            throw new IOException("文件保存失败", ex);
        }

        // 创建文档记录
        var document = new Documents
        {
            Id = Guid.NewGuid(),
            Title = uploadDto.Title?.Trim() ?? string.Empty,
            FileName = uploadDto.File.FileName,
            FilePath = filePath,
            FileType = originalExt.TrimStart('.'), // 去掉点，统一小写
            FileSize = FormatFileSize(uploadDto.File.Length),
            Description = uploadDto.Description,
            Tags = uploadDto.Tags,
            AccessLevel = NormalizeAccessLevel(uploadDto.AccessLevel),
            Status = "uploaded",
            CreatedBy = userId,
            UpdatedBy = userId,
            UserId = userId
        };

        await _documentRepository.CreateAsync(document);

        return new DocumentResponseDto
        {
            Id = document.Id,
            Title = document.Title,
            FileName = document.FileName,
            FileType = document.FileType,
            FileSize = document.FileSize,
            Description = document.Description,
            Tags = document.Tags,
            AccessLevel = document.AccessLevel,
            Status = document.Status,
            CreatedAt = document.CreatedAt,
            ChunkCount = 0
        };
    }

    public async Task<List<DocumentResponseDto>> GetUserDocumentsAsync(Guid userId)
    {
        var documents = await _documentRepository.GetByUserIdAsync(userId);

        return documents.Select(d => new DocumentResponseDto
        {
            Id = d.Id,
            Title = d.Title,
            FileName = d.FileName,
            FileType = d.FileType,
            FileSize = d.FileSize,
            Description = d.Description,
            Tags = d.Tags,
            AccessLevel = d.AccessLevel,
            Status = d.Status,
            CreatedAt = d.CreatedAt,
            ChunkCount = d.DocumentChunks.Count
        }).ToList();
    }

    public async Task<DocumentResponseDto?> GetDocumentAsync(Guid documentId)
    {
        var document = await _documentRepository.GetByIdAsync(documentId);
        if (document == null)
            return null;

        return new DocumentResponseDto
        {
            Id = document.Id,
            Title = document.Title,
            FileName = document.FileName,
            FileType = document.FileType,
            FileSize = document.FileSize,
            Description = document.Description,
            Tags = document.Tags,
            AccessLevel = document.AccessLevel,
            Status = document.Status,
            CreatedAt = document.CreatedAt,
            ChunkCount = document.DocumentChunks.Count
        };
    }

    public async Task DeleteDocumentAsync(Guid documentId, Guid userId)
    {
        var document = await _documentRepository.GetByIdAsync(documentId);
        if (document == null || document.CreatedBy != userId)
            throw new UnauthorizedAccessException("无权限删除此文档");

        // 删除文件
        if (File.Exists(document.FilePath))
        {
            File.Delete(document.FilePath);
        }

        // 删除文档（级联删除chunks和vectors）
        await _documentRepository.DeleteAsync(documentId);
    }

    public async Task<bool> ProcessDocumentAsync(Guid documentId)
    {
        var document = await _documentRepository.GetByIdAsync(documentId);
        if (document == null)
            return false;

        try
        {
            // 更新状态为处理中
            document.Status = "processing";
            await _documentRepository.UpdateAsync(document);

            // 提取文本内容
            var ext = document.FileType.StartsWith('.') ? document.FileType : "." + document.FileType;
            var text = await ExtractTextFromFileAsync(document.FilePath, ext);

            // 分块
            var chunks = await _chunkingService.ChunkTextAsync(
                text,
                _ragSettings.ChunkSize,
                _ragSettings.ChunkOverlap);

            // 处理每个分块
            for (int i = 0; i < chunks.Count; i++)
            {
                var chunk = new DocumentChunks
                {
                    Id = Guid.NewGuid(),
                    ChunkIndex = i,
                    Content = chunks[i],
                    TokenCount = EstimateTokenCount(chunks[i]),
                    Document = document,
                    CreatedBy = document.CreatedBy,
                    UpdatedBy = document.UpdatedBy
                };

                await _chunkRepository.CreateAsync(chunk);

                // 生成向量并创建知识库条目
                var embedding = await _embeddingService.GenerateTextEmbeddingAsync(chunks[i]);

                var knowledgeBase = new Domain.Entities.App.KnowledgeBase
                {
                    Id = Guid.NewGuid(),
                    Embedding = embedding,
                    ChunkId = chunk.Id,
                    Content = chunks[i],
                    DocumentType = document.FileType,
                    EmbeddingType = "text-embedding",
                    IsChunked = true,
                    Source = "file",
                    Title = document.Title,
                    CreatedBy = document.CreatedBy,
                    UpdatedBy = document.UpdatedBy
                };

                await _knowledgeBaseRepository.CreatedAsync(knowledgeBase);
            }

            // 更新状态为已完成
            document.Status = "completed";
            await _documentRepository.UpdateAsync(document);

            return true;
        }
        catch (Exception)
        {
            // 更新状态为失败
            document.Status = "failed";
            await _documentRepository.UpdateAsync(document);

            return false;
        }
    }

    public async Task<KnowledgeStatisticsDto> GetStatisticsAsync(Guid userId)
    {
        var documents = await _documentRepository.GetByUserIdAsync(userId);
        var totalChunks = documents.Sum(d => d.DocumentChunks.Count);
        return new KnowledgeStatisticsDto
        {
            TotalDocuments = documents.Count,
            CompletedDocuments = documents.Count(d => d.Status == "completed"),
            ProcessingDocuments = documents.Count(d => d.Status == "processing"),
            UploadedDocuments = documents.Count(d => d.Status == "uploaded"),
            FailedDocuments = documents.Count(d => d.Status == "failed"),
            TotalChunks = totalChunks
        };
    }

    public async Task<PagedResult<KnowledgeItemDto>> QueryKnowledgeItemsAsync(KnowledgeItemQuery query, Guid userId)
    {
        var (items, total) = await _kbRepository.QueryAsync(query.Keyword, query.DocumentId, query.DocumentType, query.Page, query.PageSize);
        // 仅筛选当前用户文档（如果需要多租户隔离；假设 Document.CreatedBy 标识所有者）
        var filtered = items.Where(k => k.Chunk?.Document.CreatedBy == userId).ToList();
        if (filtered.Count != items.Count)
        {
            // 调整 total (此处简单重计；若数据量大需优化)
            total = await _kbRepository.QueryAsync(query.Keyword, query.DocumentId, query.DocumentType, 1, int.MaxValue)
                .ContinueWith(t => t.Result.Items.Count(k => k.Chunk?.Document.CreatedBy == userId));
            filtered = filtered.Skip((query.Page - 1) * query.PageSize).Take(query.PageSize).ToList();
        }
        var dtos = filtered.Select(k => new KnowledgeItemDto
        {
            Id = k.Id,
            DocumentId = k.Chunk!.Document.Id,
            DocumentTitle = k.Title,
            Title = k.Title,
            ChunkIndex = k.Chunk.ChunkIndex,
            Preview = (k.Content ?? string.Empty).Length > 100 ? (k.Content ?? string.Empty).Substring(0, 100) + "..." : (k.Content ?? string.Empty),
            ContentLength = (k.Content ?? string.Empty).Length,
            DocumentType = k.DocumentType,
            EmbeddingType = k.EmbeddingType,
            EmbeddingDimensions = k.Embedding?.Length ?? 0,
            Source = k.Source,
            CreatedAt = k.CreatedAt
        }).ToList();
        return new PagedResult<KnowledgeItemDto>
        {
            Items = dtos,
            TotalCount = total,
            Page = query.Page,
            PageSize = query.PageSize
        };
    }

    public async Task<KnowledgeItemDetailDto?> GetKnowledgeItemAsync(Guid id, Guid userId)
    {
        var entity = await _kbRepository.GetByIdAsync(id);
        if (entity == null) return null;
        // 权限检查（所属用户）
        if (entity.Chunk?.Document.CreatedBy != userId) return null;
        return new KnowledgeItemDetailDto
        {
            Id = entity.Id,
            DocumentId = entity.Chunk!.Document.Id,
            DocumentTitle = entity.Title,
            Title = entity.Title,
            ChunkIndex = entity.Chunk.ChunkIndex,
            Preview = (entity.Content ?? string.Empty).Length > 100 ? (entity.Content ?? string.Empty).Substring(0, 100) + "..." : (entity.Content ?? string.Empty),
            ContentLength = (entity.Content ?? string.Empty).Length,
            DocumentType = entity.DocumentType,
            EmbeddingType = entity.EmbeddingType,
            EmbeddingDimensions = entity.Embedding?.Length ?? 0,
            Source = entity.Source,
            CreatedAt = entity.CreatedAt,
            FullContent = entity.Content ?? string.Empty,
            Metadata = entity.Metadata,
            IsChunked = entity.IsChunked
        };
    }

    private async Task<string> ExtractTextFromFileAsync(string filePath, string fileType)
    {
        switch (fileType.ToLowerInvariant())
        {
            case ".txt":
                return await File.ReadAllTextAsync(filePath, Encoding.UTF8);
            case ".md":
                return await File.ReadAllTextAsync(filePath, Encoding.UTF8);
            case ".pdf":
                return ExtractPdfBasic(filePath);
            case ".docx":
                return ExtractDocx(filePath);
            case ".doc":
                return ExtractLegacyDoc(filePath);
            case ".xls":
            case ".xlsx":
                return ExtractSpreadsheet(filePath);
            case ".ppt":
            case ".pptx":
                return ExtractPresentation(filePath);
            default:
                throw new NotSupportedException($"不支持的文件类型: {fileType}");
        }
    }

    private string NormalizeAccessLevel(string? level)
    {
        if (string.IsNullOrWhiteSpace(level)) return "internal";
        level = level.Trim().ToLowerInvariant();
        return level switch
        {
            "public" => "public",
            "private" => "private",
            _ => "internal"
        };
    }

    private string ExtractDocx(string path)
    {
        try
        {
            using var stream = File.OpenRead(path);
            using var doc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Open(stream, false);
            var body = doc.MainDocumentPart?.Document?.Body;
            if (body == null) return string.Empty;
            var sb = new StringBuilder();
            foreach (var para in body.Elements<DocumentFormat.OpenXml.Wordprocessing.Paragraph>())
            {
                sb.AppendLine(para.InnerText);
            }
            return sb.ToString();
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"DOCX解析失败: {ex.Message}", ex);
        }
    }

    private string ExtractLegacyDoc(string path)
    {
        // 当前未引入 HWPF (NPOI Word 解析) 依赖，返回占位文本
        return "[legacy .doc 未集成解析库，需添加 HWPF 支持]";
    }

    private string ExtractSpreadsheet(string path)
    {
        try
        {
            var sb = new StringBuilder();
            using var fs = File.OpenRead(path);
            NPOI.SS.UserModel.IWorkbook wb = Path.GetExtension(path).ToLowerInvariant() == ".xls"
                ? new NPOI.HSSF.UserModel.HSSFWorkbook(fs)
                : new NPOI.XSSF.UserModel.XSSFWorkbook(fs);
            for (int i = 0; i < wb.NumberOfSheets; i++)
            {
                var sheet = wb.GetSheetAt(i);
                if (sheet == null) continue;
                sb.AppendLine($"[Sheet:{sheet.SheetName}]");
                foreach (NPOI.SS.UserModel.IRow row in sheet)
                {
                    var cells = new List<string>();
                    foreach (var cell in row.Cells)
                    {
                        var str = cell?.ToString();
                        if (!string.IsNullOrEmpty(str)) cells.Add(str);
                    }
                    if (cells.Count > 0)
                        sb.AppendLine(string.Join("\t", cells));
                }
            }
            return sb.ToString();
        }
        catch (Exception ex)
        {
            return $"[excel 解析失败] {ex.Message}";
        }
    }

    private string ExtractPresentation(string path)
    {
        // 未引入 HSLF/XSLF 解析（需要额外 NPOI 组件），返回占位
        return "[ppt/pptx 未集成解析库，需添加 NPOI.SL 支持]";
    }

    private string ExtractPdfBasic(string path)
    {
        try
        {
            // 这里未引入专用 PDF 库, 简化为占位（可扩展 PdfPig / iText7）
            return "[PDF 占位] 需集成 PdfPig / iText7 以抽取真实文本";
        }
        catch (Exception ex)
        {
            return $"[pdf 解析失败] {ex.Message}";
        }
    }

    private int EstimateTokenCount(string text)
    {
        // 简单的token估算，通常1个token约等于0.75个单词
        var words = text.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
        return (int)(words.Length * 1.33);
    }

    private string FormatFileSize(long bytes)
    {
        string[] sizes = { "B", "KB", "MB", "GB" };
        double len = bytes;
        int order = 0;
        while (len >= 1024 && order < sizes.Length - 1)
        {
            order++;
            len = len / 1024;
        }
        return $"{len:0.##} {sizes[order]}";
    }
}
